package com.landon.id.generator.segment;

import com.landon.id.exception.BizException;
import com.landon.id.model.enums.BaseResultCodeEnum;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.math.NumberUtils;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;

/**
 * 号段对象
 *
 * @author landon_shi
 */
@Slf4j
@Setter
@Getter
@EqualsAndHashCode(callSuper = false)
public class BusinessCosIdSegmentChain extends LinkedList<BusinessCosIdSegmentChain.BusinessCosIdGenerator> {

    /**
     * 业务ID
     */
    private String businessId;

    /**
     * 安全距离长度
     */
    private volatile long safeDistanceLength;

    /**
     * 安全距离对应的ID阈值（即当id自增到这个值，那就需要触发新增新号段）
     */
    private volatile long safeDistancePointId = 0l;

    /**
     * 是否有线程正在执行追加号段
     */
    private volatile boolean doAddSegmentIsRunning = false;

    /**
     * 多线程同时竞争追加号段时使用的锁对象
     */
    private final transient Object lock = new Object();

    /**
     * 号段最后一个id
     */
    private volatile long lastId = 0l;

    /**
     * 上次预取数量
     */
    private Long lastPrefetchingCount;

    /**
     * 上次预取时间（往里面塞号段的时间）
     */
    private LocalDateTime putSegmentChainTime;

    /**
     * 饥饿时间 （上次进行新增号段的时间）
     */
    private LocalDateTime hungerTime;

    /**
     * 剩余可用的ID数量
     */
    private AtomicLong availableCount;

    /**
     * 追加号段
     */
    private transient Function<BusinessCosIdSegmentChain, BusinessCosIdGenerator> additionalSegmentFunc;

    /**
     * 控制号段追加的锁对象
     */
    private ReentrantLock reentrantLock = new ReentrantLock(true);

    public BusinessCosIdSegmentChain(String businessId,
                                     Long safeDistanceLength,
                                     Long lastPrefetchingCount,
                                     Function<BusinessCosIdSegmentChain, BusinessCosIdGenerator> additionalSegmentFunc) {
        // 业务标识
        this.businessId = businessId;
        // 安全距离长度
        this.safeDistanceLength = safeDistanceLength;
        // 上次预取的数量
        this.lastPrefetchingCount = lastPrefetchingCount;
        // 追加号段
        this.additionalSegmentFunc = additionalSegmentFunc;
        // 本号段内剩余可用号数量
        this.availableCount = new AtomicLong(0);
        // 触发追加号段
        this.doAdditionalSegment(1);
    }

    @Getter
    @Setter
    public static class BusinessCosIdGenerator {

        /**
         * 业务ID
         */
        private String businessId;

        /**
         * 号段开始位置
         */
        private Long startId;

        /**
         * 号段结束位置
         */
        private Long endId;

        /**
         * 最后一次从该号段取的id
         */
        private AtomicLong atomicLong;

        /**
         * 这个号段是否可用
         */
        private volatile Boolean available;

        public BusinessCosIdGenerator(String businessId, Long startId, Long endId) {
            this.businessId = businessId;
            this.startId = startId;
            this.endId = endId;
            this.atomicLong = new AtomicLong(startId);
            this.available = true;
        }

        /**
         * 获取分布式ID，当号段里的 id 已经用完了就需要反返回 null
         */
        public Long getId() {
            long nextId = this.atomicLong.getAndIncrement();
            if (endId > nextId) {
                return nextId;
            } else {
                this.available = false;
                return null;
            }
        }

        /**
         * 获取可用的号段数量
         */
        public Long availableCount() {
            if (Boolean.FALSE.equals(available)) {
                return NumberUtils.LONG_ZERO;
            }
            return endId - atomicLong.get();
        }

        /**
         * 号段长度
         */
        public Long length() {
            return endId - startId;
        }
    }

    /**
     * 只需要单线程触发一次，下一个线程将不会进来
     * <p>
     * 还可能会有一个问题， 当一个线程获取下一个号段为空的时候，
     * 这个时候上一个线程释放了锁了，这个时候进来是多余的，锁内应该再判断一次是否需要追加号段
     * 所以应该要双重验证
     */
    private void doAdditionalSegment(long id) {
        // 当前获取到 id 已经是当前号段内的最后一个 id
        if (id == lastId) {
            // 正常来说，当前号段内的 id 如果已经递增到了安全距离，就需要生成一个新的号段追加到号段链的尾部
            // 走到了这里代表这个号段马上用完了，如果有线程正在追加新的号段，那等一下等它追加完成
            synchronized (lock) {
                while (id == lastId && doAddSegmentIsRunning) {
                    try {
                        lock.wait(2000);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        log.error("获取id 等待超时", e);
                        throw new BizException(BaseResultCodeEnum.SYSTEM_ERROR);
                    }
                }
            }
        }
        // 没有在运行获取号段的线程 且 当前id位置已经到了或超过安全点的标记id
        // 走到这里代表当前获取的 id 已经到了安全距离的 id 位置，即将需要进行追加新号段，这个一个线程可能会慢一点，其他线程可继续向后取 id
        // 因为这里的 doAddSegmentIsRunning 会被追加新号段的线程修改成了 true，被 volatile 修饰之后的状态修改能被线程及时感知到
        // 所以这个 if 里的代码其它线程是进不来的
        if ((!doAddSegmentIsRunning && id >= safeDistancePointId)) {
            boolean tryLock = reentrantLock.tryLock();
            try {
                // 双层校验
                if (tryLock && !doAddSegmentIsRunning && id >= safeDistancePointId) {
                    hungerTime = LocalDateTime.now();
                    doAddSegmentIsRunning = true;
                    try {
                        /*
                        // 可以考虑用异步获取新号段
                        CompletableFuture.runAsync(() -> {
                            // 新增号段
                            BusinessCosIdGenerator newBusinessCosIdGenerator = additionalSegmentFunc.apply(this);
                            // 将新生成的号段接入链表上，新的号段在链表尾部
                            this.setLastNext(newBusinessCosIdGenerator);
                            doAddSegmentIsRunning = false;
                            synchronized (lock) {
                                lock.notifyAll();
                            }
                        });
                        */
                        // 同步获取新增号段
                        BusinessCosIdGenerator newBusinessCosIdGenerator = additionalSegmentFunc.apply(this);
                        // 将新生成的号段接入链表上，新的号段在链表尾部
                        this.setLastNext(newBusinessCosIdGenerator);
                        doAddSegmentIsRunning = false;
                        synchronized (lock) {
                            // 唤醒正在等待要使用新号段的线程
                            lock.notifyAll();
                        }
                    } catch (Exception e) {
                        log.error("追加号段失败", e);
                    }
                }
            } finally {
                reentrantLock.unlock();
            }
        }
    }

    /**
     * 获取id
     *
     * @return id
     */
    public Long getId() {
        // 获取链表头部第一个BusinessCosIdGenerator
        BusinessCosIdGenerator businessCosIdGenerator = this.peekFirst();
        if (Objects.isNull(businessCosIdGenerator)) {
            this.doAdditionalSegment(lastId);
            return getId();
        }
        Long id = businessCosIdGenerator.getId();
        // 当前号段已经完全使用完了
        if (Objects.isNull(id)) {
            // 获取下一个可用号段
            BusinessCosIdGenerator nextBusinessCosIdGenerator = this.getNextSegment();
            if (Objects.isNull(nextBusinessCosIdGenerator)) {
                // 如果不存在在号段 那新增一个
                this.doAdditionalSegment(lastId);
                return this.getId();
            } else {
                return nextBusinessCosIdGenerator.getId();
            }
        }
        // 触发追加号段
        this.doAdditionalSegment(id);
        return id;
    }

    /**
     * 取下一个号段
     */
    private BusinessCosIdGenerator getNextSegment() {
        synchronized (lock) {
            BusinessCosIdGenerator businessCosIdGenerator = this.peekFirst();
            if (businessCosIdGenerator == null || businessCosIdGenerator.getAvailable()) {
                return businessCosIdGenerator;
            }
            // 当前的已经不可用了，删除掉然后返回下一个号段
            // 处理前：segment1 -> segment2 -> segment3
            // 处理后：segment2 -> segment3
            // segment1被移除
            this.poll();
            return this.peekFirst();
        }
    }

    /**
     * 将新生成的号段接入链表上
     */
    private void setLastNext(BusinessCosIdGenerator nextBusinessCosIdGenerator) {
        putSegmentChainTime = LocalDateTime.now();
        // 设置号段内的可用id数量
        long availableIdCount = nextBusinessCosIdGenerator.getEndId() - nextBusinessCosIdGenerator.getStartId();
        availableCount.addAndGet(availableIdCount);

        // 在号段链尾部加入生成的号段：segment1 -> segment2 -> segment3 -> segment4(最新的)
        this.offer(nextBusinessCosIdGenerator);

        // 复制一份数据用于计算安全距离
        List<BusinessCosIdGenerator> businessCosIdGeneratorList = new ArrayList<>(this);
        // 反转，将最新的号段放在链表头部：segment4(最新的) -> segment3 -> segment2 -> segment1
        Collections.reverse(businessCosIdGeneratorList);

        // 循环从最后一个开始往前面算，根据当前的安全距离往前面推算触发安全阈值的id是哪一个
        long safeDistanceLimit = this.safeDistanceLength;
        for (BusinessCosIdGenerator businessCosIdGenerator : businessCosIdGeneratorList) {
            safeDistanceLimit = safeDistanceLimit - businessCosIdGenerator.length();
            if (safeDistanceLimit < 0) {
                this.safeDistancePointId = businessCosIdGenerator.getEndId() + safeDistanceLimit;
                return;
            }
        }
        if (this.peek() != null) {
            this.safeDistancePointId = this.peek().getStartId();
        } else {
            this.safeDistancePointId = NumberUtils.INTEGER_ZERO;
        }
    }
}